#pragma once

#include "basictypes.h"
#include "tstring.h"
#include <Windows.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

namespace bl{

class File
{
public:
	File(void) : file_(INVALID_HANDLE_VALUE)
	{
	}

	~File(void)
	{
		if(file_ != INVALID_HANDLE_VALUE)
			Close();
	}


	enum OpenAccessMode { READ, WRITE, READ_WRITE};
	enum OpenShareMode {SHARE_NONE, SHARE_READ, SHARE_WRITE, SHARE_READ_WRITE, SHARE_DELETE};
	enum ExistsMode {NEVER_FAIL, FAIL_IF_NOT_EXISTS, FAIL_IF_EXISTS};

	bool Open(const tchar* fileName, OpenAccessMode accessMode, OpenShareMode shareMode, ExistsMode existsMode)
	{
		DWORD desired_access = 0;
		switch(accessMode)
		{
		case READ:
			desired_access = GENERIC_READ;
			break;
		case WRITE:
			desired_access = GENERIC_WRITE;
			break;
		case  READ_WRITE:
			desired_access = GENERIC_READ | GENERIC_WRITE;
			break;
		}

		DWORD share_mode = 0;
		switch(shareMode)
		{
		case SHARE_NONE:
			share_mode = 0;
			break;
		case SHARE_READ:
			share_mode = FILE_SHARE_READ;
			break;
		case SHARE_WRITE:
			share_mode = FILE_SHARE_WRITE;
			break;
		case SHARE_READ_WRITE:
			share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
			break;
		case SHARE_DELETE:
			share_mode = FILE_SHARE_DELETE;
			break;
		}

		DWORD creation_disposition = 0;
		switch(existsMode)
		{
		case NEVER_FAIL:
			creation_disposition = (accessMode==READ) ? OPEN_ALWAYS : CREATE_ALWAYS;
			break;
		case FAIL_IF_NOT_EXISTS:
			creation_disposition = OPEN_EXISTING;
			break;
		case FAIL_IF_EXISTS:
			if(accessMode == READ)
				return false;
			creation_disposition = CREATE_NEW;
			break;
		}

		file_ = CreateFile(fileName, desired_access, share_mode, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL);

		return INVALID_HANDLE_VALUE != file_;
	}

	//Opens an existing file for reading
	bool OpenRead(const tchar* fileName)
	{
		return Open(fileName, READ, SHARE_READ_WRITE, FAIL_IF_NOT_EXISTS);
	}

	//Opens an existing file for writing
	bool OpenWrite(const tchar* fileName)
	{
		return Open(fileName, WRITE, SHARE_READ, FAIL_IF_EXISTS);
	}

	bool Read(uint8* buf, const uint32 len, uint32* bytesRead)
	{
		DWORD bytes_read = 0;
		if( !::ReadFile(file_, buf, len, &bytes_read, NULL))
			return false;

		if(bytesRead)
			*bytesRead = bytes_read;

		return true;
	}

	bool Write(const uint8* buf, const uint32 len, uint32* bytesWritten)
	{
		DWORD wrote = 0;
		if( !::WriteFile(file_, buf, len, &wrote, NULL) )
			return false;

		if(bytesWritten)
			*bytesWritten = wrote;

		return len==wrote;
	}

	// Forces a write of all buffered data to the file on disk.
	bool Flush()
	{
		return ::FlushFileBuffers(file_) != 0;
	}

	enum SeekMethod { SEEK_FROM_START, SEEK_FROM_CURRENT, SEEK_FROM_END };
	bool Seek(int64 offset, SeekMethod seekMethod = SEEK_FROM_START) const
	{
		DWORD move_method = 0;
		switch(seekMethod)
		{
		case SEEK_FROM_START:
			move_method = FILE_BEGIN;
			break;
		case SEEK_FROM_CURRENT:
			move_method = FILE_CURRENT;
			break;
		case SEEK_FROM_END:
			move_method = FILE_END;
			break;
		}

		LARGE_INTEGER pos;
		pos.QuadPart = offset;
		return !!::SetFilePointerEx(file_, pos, NULL, move_method);
	}

	int64 Position() const
	{
		LARGE_INTEGER zero;
		zero.QuadPart = 0;
		LARGE_INTEGER pos;
		pos.QuadPart = 0;
		return ::SetFilePointerEx(file_, zero, &pos, FILE_CURRENT) ? pos.QuadPart : -1;
	}

	// Returns the size of the file, or -1 on failure.
	int64 Size() const
	{
		LARGE_INTEGER size;
		return ::GetFileSizeEx(file_, &size) ? size.QuadPart : -1;
	}

	bool Truncate(int64 len)
	{
		if(len<0)
			return false;

		if(!Seek(len, SEEK_FROM_START))
			return false;

		return !!::SetEndOfFile(file_);
	}

	bool Close()
	{
		bool ret = !!::CloseHandle(file_);
		file_ = INVALID_HANDLE_VALUE;
		return ret;
	}


	static bool Exists(const tchar* fileName)
	{
		return 0 != ::PathFileExists(fileName);
	}

	static bool Delete(const tchar* fileName)
	{
		if(!Exists(fileName))
			return true;
		return 0 != ::DeleteFile(fileName);
	}

	static int64 GetFileSize(const tchar* fileName)
	{
		WIN32_FILE_ATTRIBUTE_DATA data;
		::ZeroMemory(&data, sizeof(data));

		if(!::GetFileAttributesEx(fileName, ::GetFileExInfoStandard, &data) )
			return -1;

		LARGE_INTEGER size;
		size.LowPart = data.nFileSizeLow;
		size.HighPart = data.nFileSizeHigh;

		return size.QuadPart;
	}



private:
#ifdef WIN32
	HANDLE file_;
#endif
};


}